package com.lry.easyExcel;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.Sheet;
import com.lry.easyExcel.utils.*;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.*;
import static com.lry.easyExcel.utils.Common.*;


/**
 * @author JiangJie
 * @create 2020-11-27 13:10
 */
@Slf4j
public class Test {

    static List<List<Modbus>> allSheet;

    public static void main(String[] args) throws FileNotFoundException {
        InputStream is= Test.class.getClassLoader().getResourceAsStream("Modbus信息表-智慧用电终端-消防专用-20200519.xls");

        //读取所有sheet
        allSheet = readAllSheet(is);
        if (allSheet.isEmpty()) {
            System.out.println("allSheet empty");
        }

        //随机选中一个sheet
//        int selectIndex = new Random().nextInt(allSheet.size());
//        int selectIndex = 6;

        for(int selectIndex=0;selectIndex<=16;selectIndex++){
            List<Modbus> oneSheet = selectOneSheet(selectIndex);
            if (null==oneSheet||oneSheet.isEmpty()) {
                log.info("oneSheet empty");
                continue;
            }
            System.out.println("selectIndex="+ selectIndex);

            List<String> readCmds = readCommand(oneSheet);
            System.out.println("readCmds = "+readCmds);

            List<String> writeCmds =  writeCommand(oneSheet);
            System.out.println("writeCmds= "+writeCmds);

            System.out.println();
        }
    }


    private static List<String> writeCommand(List<Modbus> oneSheet) {
        //给oneSheet每一行追加content内容
        addContentToRows(oneSheet);
        //计算modbus写指令
        List<String> writeCommand = calWriteCommand(oneSheet);
        return writeCommand;
    }

    private static void addContentToRows(List<Modbus> oneSheet) {
        Iterator<Modbus> it = oneSheet.iterator();
        while (it.hasNext()) {
            Modbus modbus = it.next();
            if (Common.RO.equalsIgnoreCase(modbus.getRw())) {
                it.remove();
                continue;
            }
            if (StringUtils.isEmpty(modbus.getDataFormat())) {
                it.remove();
                continue;
            }
            if (null == modbus.getRegisterNum()) {
                it.remove();
                continue;
            }
            String input = null;
            switch (modbus.getDataFormat().toUpperCase()) {
                case DATA_FORMAT_BIN:
                    //如果是bin格式，内容长度必须是registerNum的四倍，而且字符只能是0~9，a~f
                    int registerNum = modbus.getRegisterNum();//3
                    input = "202000110030";

                    int needLen = registerNum * 4;
                    int inputLen = input.length();
                    if (inputLen > needLen) {
                        //registerNum=3, needLen=12, inputLen=14, input = "202000110030ff"----> "2000110030ff"
                        input = input.substring(inputLen - needLen, inputLen);
                    } else if (inputLen < needLen) {
                        //高位补0
                        //registerNum=4, needLen=16, inputLen=14, input = "202000110030ff"----> "00202000110030ff"
                        StringBuilder sb = new StringBuilder();
                        for (int i = 0; i < needLen - inputLen; i++) {
                            sb.append("0");
                        }
                        sb.append(input);
                        input = sb.toString();
                    }
                    if (!ByteUtils.checkHex(input)) {
                        log.info("BIN格式只能包含0~9，a~f");
                        input = null;
                    }
                    break;
                case DATA_FORMAT_UINT16:
                    //无符号16位数（0~2^16-1）
                    input = "1";
                    try {
                        int uint16 = Integer.parseInt(input);
                        if (uint16 < 0 || uint16 > Math.pow(2, 16) - 1) {
                            input = null;
                            log.info("uint16格式，数字范围超出");
                            break;
                        }
                        //用int表示2字节的无符号整数，实际上16进制长度为4
                        input = ByteUtils.toHexStr(uint16, 4);
                    } catch (Exception e) {
                        input = null;
                        log.info("uint16格式，数字范围超出" + e.getMessage());
                    }
                    break;
                case DATA_FORMAT_INT16:
                    //有符号16位数（Short.MIN_VALUE～Short.MAX_VALUE）
                    input = "1";
                    try {
                        short int16 = Short.parseShort(input);
                        input = ByteUtils.toHexStr(int16);
                    } catch (Exception e) {
                        input = null;
                        log.info("int16格式，数字范围超出" + e.getMessage());
                    }
                    break;
                case DATA_FORMAT_UINT32:
                    //无符号32位数(0~2^32-1)
                    input = "1";
                    try {
                        long uint32 = Long.parseLong(input);
                        if (uint32 < 0 || uint32 > Math.pow(2, 32) - 1) {
                            input = null;
                            log.info("uint32格式，数字范围超出");
                            break;
                        }
                        //用int表示2字节的无符号整数，实际上16进制长度为4
                        input = ByteUtils.toHexStr(uint32, 8);
                    } catch (Exception e) {
                        input = null;
                        log.info("uint32格式，数字范围超出" + e.getMessage());
                    }
                    break;
                case DATA_FORMAT_INT32:
                    //有符号32位数(Integer.MIN_VALUE~Integer.MAX_VALUE)
                    input = "1";
                    try {
                        int int32 = Integer.parseInt(input);
                        input = ByteUtils.toHexStr(int32);
                    } catch (Exception e) {
                        input = null;
                        log.info("int32格式，数字范围超出" + e.getMessage());
                    }
                    break;
                case DATA_FORMAT_FLOAT:
                    input = "316.8";
                    try {
                        float f = Float.parseFloat(input);
                        input = ByteUtils.toHexStr(f);
                    } catch (Exception e) {
                        input = null;
                        log.info("float格式错误，数字范围超出" + e.getMessage());
                    }
                    break;
                case DATA_FORMAT_IP:
                    //四个寄存器，8个字节
                    input = "120.237.153.77";//00 78    00 ED     00 99      00 4D
                    String[] split = input.split("\\.");
                    if (null == split || split.length != 4) {
                        input = null;
                        log.info("ip格式错误，请遵循格式x.x.x.x");
                        break;
                    }
                    StringBuilder sb = new StringBuilder();
                    for (String str : split) {
                        try {
                            short s = Short.parseShort(str);
                            if (s < 0 || s > 255) {
                                input = null;
                                log.info("ip格式错误，请遵循格式x.x.x.x,每一个x必须是0~255");
                                break;
                            }
                            sb.append(ByteUtils.toHexStr(s));
                        } catch (Exception e) {
                            input = null;
                            log.info("ip格式错误，请遵循格式x.x.x.x,每一个x必须是0~255");
                            break;
                        }
                    }
                    input = sb.toString();
                    break;
                case DATA_FORMAT_STRING:
                    //n个寄存器，2*n个字节，string长度不得超过2*n(多了的话把后面多的截取掉), 不足的话用00在后面补齐,
                    input = "AB";
                    registerNum = modbus.getRegisterNum();

                    needLen = 2 * registerNum;
                    inputLen = input.length();
                    if (inputLen < needLen) {
                        //registerNum=2，needLen=4，inputLen=2，input="AB"--->"AB00"
                        StringBuilder sb1 = new StringBuilder(input);
                        for (int i = 0; i < needLen - inputLen; i++) {
                            sb1.append("0");
                        }
                        input = sb1.toString();
                    } else if (inputLen > needLen) {
                        input = input.substring(0, needLen);
                    }
                    byte[] inputBytes = input.getBytes();//todo utf-8
                    input = ByteUtils.toHexStr(inputBytes);
                    break;
                default:
                    log.info("格式错误,没有格式" + modbus.getDataFormat().toUpperCase());
                    input = null;
                    break;
            }

            if (StringUtils.isEmpty(input)) {
                it.remove();
            } else {
                modbus.setContent(input);
            }
        }
    }

    private static List<String> readCommand(List<Modbus> oneSheet) {
        //用户在当前sheet选中的行号，0开始算
        List<Integer> curSheetSelectRowIndex = selectCurSheetRowIndex(oneSheet);

        //当前sheet选中行（用于读指令）
        List<Modbus> curSheetSelectRows = selectCurSheetRows(oneSheet, curSheetSelectRowIndex);

        //计算modbus读指令
        List<String> readCommand = calReadCommand(curSheetSelectRows);

        return readCommand;
    }

    //寄存器个数为1时都是写单个寄存器，报文都是单独的
    //寄存器个数为多个时写多个寄存器，这时候报文是否粘在一起要依据寄存器地址+寄存器个数来判断
    private static List<String> calWriteCommand(List<Modbus> selected) {
        List<String> commands = new ArrayList<>();
        if (null == selected || selected.isEmpty()) {
            return commands;
        }

        boolean f = true;//true代表是一条新的指令，false代表当前指令还未处理完毕
        int curRegisterAddress = 0;//当前寄存器地址
        int curRegisterNum = 0;//当前寄存器个数
        int sumRegisterNum = 0;//当前命令寄存器总个数
        int startRegisterAddress = 0;//当前命令寄存器的开始地址
        String curContent = "";//当前数据内容
        String sumContent = "";//数据总内容

        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < selected.size(); i++) {
            Modbus modbus = selected.get(i);
            if (!checkModbus(modbus, false)) {
                continue;
            }

            curContent = modbus.getContent();
            //如果上条指令已全部处理完毕
            if (f) {
                sumContent = curContent;
            } else {
                sumContent += curContent;
            }

            curRegisterAddress = modbus.getRegisterAddress();
            //如果上条指令已全部处理完毕
            if (f) {
                startRegisterAddress = curRegisterAddress;
            }

            curRegisterNum = modbus.getRegisterNum();
            //如果上条指令已全部处理完毕
            if (f) {
                sumRegisterNum = curRegisterNum;
            } else {
                sumRegisterNum += curRegisterNum;
            }


            //计算出下个寄存器地址
            int nextRegisterAddress = curRegisterAddress + curRegisterNum;
            //如果下个地址和我算的一样&&并且下个地址寄存器个数不能为1&&并且当前寄存器不能为1，就返回false把指令结合
            if (i < selected.size() - 1 &&
                    nextRegisterAddress == selected.get(i + 1).getRegisterAddress() &&
                    selected.get(i + 1).getRegisterNum() != 1 &&
                    selected.get(i).getRegisterNum() != 1) {
                f = false;
            } else {
                f = true;

                //数据内容总长度，注意这个sumContent是16进制串，所以数据字节数应是sumContentLen/2
                int sumContentLen = sumContent.length();
                //如果长度是奇数
                if ((sumContentLen & 1) != 0) {
                    continue;
                }
                //数据字节数,协议占一个字节
                int dataByteNum = sumContentLen >>> 1;
                if (dataByteNum <= 0 || dataByteNum > Byte.MAX_VALUE) {
                    log.info("数据字节总数大于1字节可容纳最大数127");
                    continue;
                }

                //封装指令
                String partCommand;
                //写单个寄存器
                if (modbus.getRegisterNum() == 1) {
                    partCommand = sb.append(ByteUtils.toHexStr(Common.ADDRESS)).
                            append(ByteUtils.toHexStr(WRITE_SINGLE_FUNC)).
                            append(ByteUtils.toHexStr((short) startRegisterAddress)).
                            append(sumContent).toString();
                } else {
                    //写多个寄存器
                    partCommand = sb.append(ByteUtils.toHexStr(Common.ADDRESS)).
                            append(ByteUtils.toHexStr(WRITE_MULTI_FUNC)).
                            append(ByteUtils.toHexStr((short) startRegisterAddress)).
                            append(ByteUtils.toHexStr((short) sumRegisterNum)).
                            append(ByteUtils.toHexStr((byte) dataByteNum)).
                            append(sumContent).toString();
                }
                String crc16 = Crc16Utils.getCRC(ByteUtils.hexToBytes(partCommand));
                sb.append(crc16);
                commands.add(sb.toString());
                sb.setLength(0);
            }
        }
        return commands;
    }


    /**
     * @param selected 当前sheet所有行
     * @return 读指令
     */
    public static List<String> calReadCommand(List<Modbus> selected) {
        List<String> commands = new ArrayList<>();
        if (null == selected || selected.isEmpty()) {
            return commands;
        }

        //ByteBuf buf = Unpooled.buffer();

        boolean f = true;//true代表是一条新的指令，false代表当前指令还未处理完毕
        int curRegisterAddress = 0;//当前寄存器地址
        int curRegisterNum = 0;//当前寄存器个数
        int sumRegisterNum = 0;//当前命令寄存器总个数
        int startRegisterAddress = 0;//当前命令寄存器的开始地址

        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < selected.size(); i++) {
            Modbus modbus = selected.get(i);
            if (!checkModbus(modbus, true)) {
                continue;
            }

            curRegisterAddress = modbus.getRegisterAddress();
            if (f) {
                startRegisterAddress = curRegisterAddress;
            }

            curRegisterNum = modbus.getRegisterNum();
            //如果上条指令已全部处理完毕
            if (f) {
                sumRegisterNum = curRegisterNum;
            } else {
                sumRegisterNum += curRegisterNum;
            }
            //计算出下个寄存器地址
            int nextRegisterAddress = curRegisterAddress + curRegisterNum;
            //如果下个地址和我算的一样，说明需要把指令结合
            if (i < selected.size() - 1 && nextRegisterAddress == selected.get(i + 1).getRegisterAddress()) {
                f = false;
            } else {
                f = true;

                //封装指令
                String partCommand = sb.append(ByteUtils.toHexStr(Common.ADDRESS)).
                        append(ByteUtils.toHexStr(Common.READ_FUNC)).
                        append(ByteUtils.toHexStr((short) startRegisterAddress)).
                        append(ByteUtils.toHexStr((short) sumRegisterNum)).toString();
                String crc16 = Crc16Utils.getCRC(ByteUtils.hexToBytes(partCommand));
                sb.append(crc16);
                commands.add(sb.toString());
                sb.setLength(0);
            }
        }

        return commands;
    }

    public static boolean checkModbus(Modbus modbus, boolean isRead) {
        if (null == modbus)
            return false;
        if (null == modbus.getRegisterAddress()) {
            return false;
        }
        if (null == modbus.getRegisterNum()) {
            return false;
        }
        if (StringUtils.isEmpty(modbus.getRw())) {
            return false;
        }
        if (StringUtils.isEmpty(modbus.getDataFormat())) {
            return false;
        }
        String rw = modbus.getRw().toUpperCase();
        //读多个寄存器检查参数
        if (isRead) {
            if (rw.equals(Common.WO)) {
                return false;
            }

        } else {
            if (rw.equals(Common.RO)) {
                return false;
            }
            //内容，格式校验
            if (StringUtils.isEmpty(modbus.getContent())) {
                return false;
            }
            //content全是16进制串，一个寄存器两个字节，一个字节两个16进制字符，一个寄存器对应四个16进制字符
            if (modbus.getContent().length() != modbus.getRegisterNum() * 4) {
                return false;
            }
        }
        return true;
    }

    private static List<Modbus> selectCurSheetRows(List<Modbus> oneSheet, List<Integer> curSheetSelectRowIndex) {
        List<Modbus> curSheetSelectRows = new ArrayList<>();
        if (null == curSheetSelectRowIndex || curSheetSelectRowIndex.isEmpty())
            return curSheetSelectRows;

        for (Integer i : curSheetSelectRowIndex) {
            if (i < 0 || i >= oneSheet.size())
                continue;
            curSheetSelectRows.add(oneSheet.get(i));
        }
        return curSheetSelectRows;
    }


    public static List<Integer> selectCurSheetRowIndex(List<Modbus> modbuses) {
        List<Integer> rows = new ArrayList<>();
        for (int i = 0; i < modbuses.size(); i++) {
            rows.add(i);
        }
        return rows;
    }

    public static List<Modbus> selectOneSheet(int i) {
        if (i < 0 || i >= allSheet.size()) {
            return null;
        }
        return allSheet.get(i);
    }

    public static List<List<Modbus>> readAllSheet(InputStream is) {

        final List<Modbus> rows = new ArrayList();

        ExcelReader excelReader = EasyExcelFactory.getReader(is, new AnalysisEventListener<Modbus>() {
            @Override
            public void invoke(Modbus o, AnalysisContext analysisContext) {
                rows.add(o);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {

            }
        });

        List<List<Modbus>> allSheet = new ArrayList<>();

        List<Sheet> sheets = excelReader.getSheets();
        for (Sheet sheet : sheets) {
            sheet.setHeadLineMun(1);
            sheet.setClazz(Modbus.class);
            excelReader.read(sheet);

            allSheet.add(new ArrayList<>(rows));
            rows.clear();
        }
        return allSheet;
    }

    // 简单读取 (同步读取)
    public static void simpleRead(String path) {
        try {
            InputStream is = new BufferedInputStream(new FileInputStream(path));

            // sheetNo --> 读取哪一个 表单
            // headLineMun --> 从哪一行开始读取( 不包括定义的这一行，比如 headLineMun为2 ，那么取出来的数据是从 第三行的数据开始读取 )
            // clazz --> 将读取的数据，转化成对应的实体，需要 extends BaseRowModel
            Sheet sheet = new Sheet(16, 1, Modbus.class);

            // 这里 取出来的是 ExcelModel实体 的集合
            List<Object> readList = EasyExcelFactory.read(is, sheet);

            // 存 ExcelMode 实体的 集合
            List<Modbus> list = new ArrayList<Modbus>();
            for (Object obj : readList) {
                list.add((Modbus) obj);
            }
            System.out.println(list);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
